package mediasManagement;



import hibernateUtils.HibernateUtil;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import java.util.Collection;



import daos.DAOs;
import exceptions.BadParametersException;
import exceptions.LentBookException;
import exceptions.LentMediaException;
import exceptions.MediaExistsException;
import exceptions.ReservationMediaException;

import java.util.LinkedList;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.hibernate.Session;

import loansManagement.Loan;
import loansManagement.Reservation;




/**
 * @uml.dependency   supplier="mediasManagement.FabricCategory"
 */
@Entity
@Table(name="t_media")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "media_type",
		discriminatorType = DiscriminatorType.STRING
)
@NamedQuery(name="findAllMedias", query="select m from Media m")
public abstract class Media  {

	/**
	 * Give the title of media
	 * @uml.property  name="title"
	 */
	private String title;

	/**
	 * Getter of the property <tt>title</tt>
	 * @return  Returns the title.
	 * @uml.property  name="title"
	 */
	public String getTitle() {
		return title;
	}

	/**
	 * Setter of the property <tt>title</tt>
	 * @param title  The title to set.
	 * @uml.property  name="title"
	 */
	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * Give the authors (for a book) or directors (for a DVD)
	 * @uml.property   name="authors"
	 */
	@Transient
	private List<String> authors ;

	/** 
	 * Getter of the property <tt>authors</tt>
	 * @return  Returns the authors.
	 * @uml.property  name="authors"
	 */
	public List<String> getAuthors() {
		return authors;
	}

	/** 
	 * Setter of the property <tt>authors</tt>
	 * @param authors  The authors to set.
	 * @uml.property  name="authors"
	 */
	public void setAuthors(List<String> authors) {
		this.authors = authors;
	}

	/**
	 * Give the edition date of media
	 * @uml.property  name="editionDate"
	 */

	private Calendar editionDate;

	/**
	 * Getter of the property <tt>editionDate</tt>
	 * @return  Returns the editionDate.
	 * @uml.property  name="editionDate"
	 */
	public Calendar getEditionDate() {
		return editionDate;
	}

	/**
	 * Setter of the property <tt>editionDate</tt>
	 * @param editionDate  The editionDate to set.
	 * @uml.property  name="editionDate"
	 */
	public void setEditionDate(Calendar editionDate) {
		this.editionDate = editionDate;
	}

	/**
	 * the type of the media
	 * @uml.property  name="type"
	 */
	private String type;

	/**
	 * Getter of the property <tt>type</tt>
	 * @return  Returns the type.
	 * @uml.property  name="type"
	 */
	public String getType() {
		return type;
	}

	/**
	 * Setter of the property <tt>type</tt>
	 * @param type  The type to set.
	 * @uml.property  name="type"
	 */
	public void setType(String type) {
		this.type = type;
	}


    /**
	 * Give the number of exemplaries which are created
	 * @uml.property  name="createdExemplariesNumber"
	 */

	private int createdExemplariesNumber = 0;

	/** 
	 * Getter of the property <tt>exemplariesNumber</tt>
	 * @return  Returns the exemplariesNumber.
	 * @uml.property  name="createdExemplariesNumber"
	 */
	public int getCreatedExemplariesNumber() {
		return createdExemplariesNumber;
	}

	/** 
	 * Setter of the property <tt>exemplariesNumber</tt>
	 * @param exemplariesNumber  The exemplariesNumber to set.
	 * @uml.property  name="createdExemplariesNumber"
	 */
	public void setCreatedExemplariesNumber(
			int createdExemplariesNumber) {
				this.createdExemplariesNumber = createdExemplariesNumber;
			}
	/**
	 * increment the value of created exemplaries of a media
	 */
	public void incrementCreatedExemplaries(){
		this.createdExemplariesNumber++;
	}
	

	/**
	 * @uml.property  name="LentExemplariesNumber"
	 */

	private int lentExemplariesNumber;

	/**
	 * Getter of the property <tt>LentExemplariesNumber</tt>
	 * @return  Returns the lentExemplariesNumber.
	 * @uml.property  name="LentExemplariesNumber"
	 */
	public int getLentExemplariesNumber() {
		lentExemplariesNumber = 0;
		for (Exemplary ex : exemplaries) {
			if (ex.isLent())
				lentExemplariesNumber++;
		}
		return lentExemplariesNumber;
	}

	/**
	 * Setter of the property <tt>LentExemplariesNumber</tt>
	 * @param LentExemplariesNumber  The lentExemplariesNumber to set.
	 * @uml.property  name="LentExemplariesNumber"
	 */
	public void setLentExemplariesNumber(
			int lentExemplariesNumber) {
				this.lentExemplariesNumber = lentExemplariesNumber;
			}
	/**
	 * this constructor add one exemplary default
	 * @param title
	 * @param authors
	 * @param categories
	 * @param editionDate
	 * @param mediaNumberId
	 * @param type
	 * @param fabric permits to avoid double creation of categories objects
	 *               flyweight pattern
	 * @throws BadParametersException
	 * @throws MediaExistsException
	 */
	public Media(String title, List<String> authors,ArrayList<String> categories, Calendar editionDate,String mediaNumberId,
			String type,FabricCategory fabric) throws BadParametersException, MediaExistsException{
		
			if ((title == null) || (authors == null) || (editionDate == null)
					||(categories==null)|| (type == null))
				throw new BadParametersException();

			this.title = title;
			this.authors = authors;
			this.editionDate = editionDate;
			this.mediaNumberId=mediaNumberId;
			this.addcategories(categories,fabric);
			this.type=type;
			this.exemplaries= new HashSet<Exemplary>();
			Exemplary ex= new Exemplary(this);
			this.addExemplary(ex);
			this.loans=new HashSet<Loan>();
			this.reservations=new HashSet<Reservation>();
	}
	
	public Media()
	{}
	/**
	 * this constructor permits to set automatically many exemplaries
	 * of a media
	 * @param title
	 * @param authors
	 * @param categories
	 * @param editionDate
	 * @param mediaNumberId
	 * @param type
	 * @param numberExemplary
	 * @param fabric permits to avoid double creation of categories objects
	 *               flyweight pattern
	 * @throws BadParametersException
	 * @throws MediaExistsException
	 */
	public Media(String title, ArrayList<String> authors,ArrayList<String> categories, Calendar editionDate,String mediaNumberId,
			String type,int numberExemplary,FabricCategory fabric) throws BadParametersException, MediaExistsException{
		
			if ((title == null) || (authors == null) || (editionDate == null)
					||(categories==null)|| (type == null))
				throw new BadParametersException();

			this.title = title;
			this.authors = authors;
			this.editionDate = editionDate;
			this.mediaNumberId=mediaNumberId;
			this.type=type;
			this.addcategories(categories,fabric);
			this.exemplaries= new HashSet<Exemplary>();
			for (int i=0;i<numberExemplary;i++){
			Exemplary ex= new Exemplary(this);
			this.addExemplary(ex);
			}
			this.loans=new HashSet<Loan>();
			this.reservations=new HashSet<Reservation>();
	}
	/** 
	 * Give the ISBN (for book) or ISSN (for DVD)
	 * @uml.property  name="mediaNumberId"
	 */
	
	public String getMediaNumberId() {
		return mediaNumberId;
	}
	

	/** 
	 * Setter of the property <tt>serialNumber</tt>
	 * @param serialNumber  The serialNumber to set.
	 * @uml.property  name="mediaNumberId"
	 */
	public void setMediaNumberId(
			String mediaNumberId) {
				this.mediaNumberId = mediaNumberId;
			}
	

	

	/**
	 * @uml.property  name="exemplaries"
	 * @uml.associationEnd  multiplicity="(1 -1)" ordering="true" inverse="media:mediasManagement.Exemplary"
	 */
	@OneToMany(mappedBy="media" ,fetch=FetchType.LAZY)
	private Set<Exemplary> exemplaries;

	/**
	 * Getter of the property <tt>exemplaries</tt>
	 * @return  Returns the exemplaries.
	 * @uml.property  name="exemplaries"
	 */
	public Set<Exemplary> getExemplaries() {
		return exemplaries;
	}

	/**
	 * Setter of the property <tt>exemplaries</tt>
	 * @param exemplaries  The exemplaries to set.
	 * @uml.property  name="exemplaries"
	 */
	public void setExemplaries(Set<Exemplary> exemplaries) {
		this.exemplaries = exemplaries;
	}

	@Override
	public boolean equals(Object media) {
		
		if ( media == null)
			return false;
		
		Media m = (Media) media;
		if(m.mediaNumberId==mediaNumberId)
			return true;
		
		return false;
	}

	public Exemplary availableExemplary() {
		Exemplary exemplary= null;
		for(Exemplary ex: exemplaries){
			if (!(ex.isLent())){
			//if (ex!=null){
				exemplary=ex;
				break;
			}
		}
		return exemplary;
	}

	


	public void returnMedia() {
	}

	/**
	 * @uml.property   name="categories"
	 * @uml.associationEnd   multiplicity="(1 -1)" ordering="true" inverse="medias:mediasManagement.Category"
	 */
	@Transient
	private ArrayList<Category> categories= new ArrayList<Category>();

	/** 
	 * Getter of the property <tt>categories</tt>
	 * @return  Returns the categories.
	 * @uml.property  name="categories"
	 */
	public ArrayList<Category> getCategories() {
		return categories;
	}

	/** 
	 * Setter of the property <tt>categories</tt>
	 * @param categories  The categories to set.
	 * @uml.property  name="categories"
	 */
	public void setCategories(ArrayList<Category> categories) {
		this.categories = categories;
	}

		
		/**
		 * @throws BadParametersException 
		 * @throws MediaExistsException 
		 */
	public void addExemplary(Exemplary exemplary) throws BadParametersException, MediaExistsException{
		
		if (exemplary == null)
			throw new BadParametersException();
		
		if (IsAExemplary(exemplary))
			throw new MediaExistsException();
		
		this.exemplaries.add(exemplary);
		this.incrementCreatedExemplaries();
		numberAvailableExemplary++;
	
		}
	/**
	 * 
	 * @throws BadParametersException
	 * @throws MediaExistsException
	 */
	public void addExemplary() throws BadParametersException, MediaExistsException{
		
		
		
		Exemplary exemplary	 = new Exemplary(this);
		
		this.exemplaries.add(exemplary);
		this.incrementCreatedExemplaries();
		numberAvailableExemplary++;
		
		}

		private boolean IsAExemplary(Exemplary exemplary) throws BadParametersException {
			
			if (exemplary == null)
				throw new BadParametersException();
			
			// If the object is in the list,
			// it is a known exemplary
			return exemplaries.contains(exemplary);
		}

			
			/**
			 * add  categories in the list of categories of a media
			 * @throws MediaExistsException 
			 * @throws BadParametersException 
			 */
	public void addcategories(ArrayList<String>  categoryNames, FabricCategory fabric) throws BadParametersException, MediaExistsException{
		
		for(int i = 0; i < categoryNames.size(); i++)
        {
			categories.add(fabric.getCategory(categoryNames.get(i), this));
        } 
		
	}
	
	/**
	 * add a category in the list of categories of a media
	 * @param categoryName
	 * @param fabric
	 * @throws BadParametersException
	 * @throws MediaExistsException
	 */
	public void addcategory(String categoryName, FabricCategory fabric) throws BadParametersException, MediaExistsException{
			
			categories.add(fabric.getCategory(categoryName, this));
			
			
		}

	/** 
	 * Give the ISBN (for book) or ISSN (for DVD)
	 * @uml.property name="mediaNumberId"
	 */
	

	
	/**
	 * Give the ISBN (for book) or ISSN (for DVD)
	 * @uml.property   name="mediaNumberId"
	 */
	@Id
	@Column(name="MEDIA_ID",unique = true)
	private String mediaNumberId;

	
	/**
	 * description of the media
	 */
	public String toString(){
		StringBuilder sb= new StringBuilder();
		Iterator iterator = exemplaries.iterator();
		for(Exemplary ex: exemplaries){
			sb.append(ex.getExemplaryId()+"\n");
		}
		
		return "this "+this.type+": "+this.title+"; isbn:"+this.mediaNumberId+" "+"\n"+
	"contains "+this.getExemplaries().size()+" "+ "exemplary(ies)\n"+sb;	
		
	}

	public Exemplary lend(Loan loan) throws  LentMediaException, BadParametersException  {
		// TODO Auto-generated method stub
		if (loan == null)
			throw new BadParametersException();
		
		Exemplary exemplary= availableExemplary();
		if (exemplary==null)
			throw new LentMediaException("Not Available exemplaries for this media");
		exemplary.lend(loan);
		Session session= HibernateUtil.openSession();
        session.merge(this);
		return  exemplary;
	}

		
		

			
			

				/**
				 * @uml.property  name="loans"
				 * @uml.associationEnd  multiplicity="(0 -1)" inverse="media:loansManagement.Loan"
				 */
				@Transient
				private Set<Loan> loans;

				/**
				 * Getter of the property <tt>loans</tt>
				 * @return  Returns the loans.
				 * @uml.property  name="loans"
				 */
				public Set getLoans() {
					return loans;
				}

				/**
				 * Setter of the property <tt>loans</tt>
				 * @param loans  The loans to set.
				 * @uml.property  name="loans"
				 */
				public void setLoans(Set loans) {
					this.loans = loans;
				}

				/**
				 * @uml.property  name="numberAvailableExemplary"
				 */
				private int numberAvailableExemplary;

				/**
				 * Getter of the property <tt>numberAvailableExemplary</tt>
				 * @return  Returns the numberAvailableExemplary.
				 * @uml.property  name="numberAvailableExemplary"
				 */
				public int getNumberAvailableExemplary() {
					numberAvailableExemplary = 0;
					for (Exemplary ex : exemplaries) {
						if (!(ex.isLent()||ex.isReserved()))
							numberAvailableExemplary++;
					}
					return numberAvailableExemplary;
				}

				/**
				 * Setter of the property <tt>numberAvailableExemplary</tt>
				 * @param numberAvailableExemplary  The numberAvailableExemplary to set.
				 * @uml.property  name="numberAvailableExemplary"
				 */
				public void setNumberAvailableExemplary(
						int numberAvailableExemplary) {
							this.numberAvailableExemplary = numberAvailableExemplary;
						}

				/**
				 * @uml.property  name="reservations"
				 * @uml.associationEnd  multiplicity="(0 -1)" inverse="media:loansManagement.Reservation"
				 */
				@OneToMany(mappedBy="pk.media",cascade=CascadeType.ALL)
				private Set<Reservation> reservations;

				/**
				 * Getter of the property <tt>reservations</tt>
				 * @return  Returns the reservations.
				 * @uml.property  name="reservations"
				 */
				public Set<Reservation> getReservations() {
					return reservations;
				}

				/**
				 * Setter of the property <tt>reservations</tt>
				 * @param reservations  The reservations to set.
				 * @uml.property  name="reservations"
				 */
				public void setReservations(Set<Reservation> reservations) {
					this.reservations = reservations;
				}

				public void reserve(Reservation reservation) throws BadParametersException, ReservationMediaException {
					// TODO Auto-generated method stub
					if(reservation==null)
						throw new BadParametersException();
					
					if(reservations.size()>=exemplaries.size())
						throw new ReservationMediaException();
				
					reservations.add(reservation);
					
					Session session= HibernateUtil.openSession();
			        session.merge(this);
				}

}
